// 2013年版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package types

import (
	"fmt"
	"go/ast"
	"go/constant"
	"go/internal/typeparams"
	"go/token"
	"sort"
	"strconv"
	"strings"
	"unicode"
)

// declInfo描述包级别的常量、类型、变量或func声明。
type declInfo struct {
	file      *Scope        // 包含此声明的文件的作用域
	lhs       []*Var        // n:1变量声明的lhs，或nil
	vtyp      ast.Expr      // 类型或nil（仅适用于常量和变量声明）
	init      ast.Expr      // init/orig表达式或nil（仅适用于常量和变量声明）
	inherited bool          // 如果已设置，则init表达式将从以前的常量声明继承
	tdecl     *ast.TypeSpec // 类型声明，或nil
	fdecl     *ast.FuncDecl // func声明，或nil

	// deps字段跟踪初始化表达式依赖项。
	deps map[Object]bool // 懒洋洋地初始化
}

// hasInitializer报告声明的对象是否具有初始化
// 表达式或函数体。
func (d *declInfo) hasInitializer() bool {
	return d.init != nil || d.fdecl != nil && d.fdecl.Body != nil
}

// addDep将obj添加到d的初始化表达式所依赖的对象集中。
func (d *declInfo) addDep(obj Object) {
	m := d.deps
	if m == nil {
		m = make(map[Object]bool)
		d.deps = m
	}
	m[obj] = true
}

// arityMatch检查常量或变量decl的lhs和rhs
// 具有适当数量的名称和初始表达式。常数
// decls，init是提供init expr的值规范；对于
// var decls，init为nil（在本例中，init表达式为s）。
func (check *Checker) arityMatch(s, init *ast.ValueSpec) {
	l := len(s.Names)
	r := len(s.Values)
	if init != nil {
		r = len(init.Values)
	}

	const code = _WrongAssignCount
	switch {
	case init == nil && r == 0:
		// 无初始表达式的var decl
		if s.Type == nil {
			check.errorf(s, code, "missing type or init expr")
		}
	case l < r:
		if l < len(s.Values) {
			// 来自s的初始化表达式
			n := s.Values[l]
			check.errorf(n, code, "extra init expr %s", n)
			// TODO（gri）避免此处声明但未使用的错误
		} else {
			// init exprs“继承”
			check.errorf(s, code, "extra init expr at %s", check.fset.Position(init.Pos()))
			// TODO（gri）避免此处声明但未使用的错误
		}
	case l > r && (init != nil || r != 1):
		n := s.Names[r]
		check.errorf(n, code, "missing init expr for %s", n)
	}
}

func validatedImportPath(path string) (string, error) {
	s, err := strconv.Unquote(path)
	if err != nil {
		return "", err
	}
	if s == "" {
		return "", fmt.Errorf("empty string")
	}
	const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD"
	for _, r := range s {
		if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) {
			return s, fmt.Errorf("invalid character %#U", r)
		}
	}
	return s, nil
}

// declarePkgObj在包范围内声明obj，记录其标识->obj映射，
// 并更新check.objMap。对象不能是函数或方法。
func (check *Checker) declarePkgObj(ident *ast.Ident, obj Object, d *declInfo) {
	assert(ident.Name == obj.Name())

	// spec:“名为init的包范围或文件范围标识符”
	// 只能声明为具有此（func（））签名的函数。“
	if ident.Name == "init" {
		check.errorf(ident, _InvalidInitDecl, "cannot declare init - must be func")
		return
	}

	// 规范：“主包必须有包名main和declare
	// 不带参数且不返回值的函数main。“
	if ident.Name == "main" && check.pkg.name == "main" {
		check.errorf(ident, _InvalidMainDecl, "cannot declare main - must be func")
		return
	}

	check.declare(check.pkg.scope, ident, obj, token.NoPos)
	check.objMap[obj] = d
	obj.setOrder(uint32(len(check.objMap)))
}

// filename返回适合调试输出的文件名。
func (check *Checker) filename(fileNo int) string {
	file := check.files[fileNo]
	if pos := file.Pos(); pos.IsValid() {
		return check.fset.File(pos).Name()
	}
	return fmt.Sprintf("file[%d]", fileNo)
}

func (check *Checker) importPackage(at positioner, path, dir string) *Package {
	// 如果给定的（路径，目录）已经有一个包
	// 配对，使用它而不是执行完全导入。
	// Checker.impMap仅缓存标记为完成的包
	// 或伪造（导入失败的虚拟包）。不完整但
	// 非假冒包装确实需要导入才能完成。
	key := importKey{path, dir}
	imp := check.impMap[key]
	if imp != nil {
		return imp
	}

	// 还没有包=>导入它
	if path == "C" && (check.conf.FakeImportC || check.conf.go115UsesCgo) {
		imp = NewPackage("C", "C")
		imp.fake = true // 未填充包范围
		imp.cgo = check.conf.go115UsesCgo
	} else {
		// 普通进口
		var err error
		if importer := check.conf.Importer; importer == nil {
			err = fmt.Errorf("Config.Importer not installed")
		} else if importerFrom, ok := importer.(ImporterFrom); ok {
			imp, err = importerFrom.ImportFrom(path, dir, 0)
			if imp == nil && err == nil {
				err = fmt.Errorf("Config.Importer.ImportFrom(%s, %s, 0) returned nil but no error", path, dir)
			}
		} else {
			imp, err = importer.Import(path)
			if imp == nil && err == nil {
				err = fmt.Errorf("Config.Importer.Import(%s) returned nil but no error", path)
			}
		}
		// 确保我们有一个有效的包名
		// （此处的错误只能在创建包后通过操作包发生）
		if err == nil && imp != nil && (imp.name == "_" || imp.name == "") {
			err = fmt.Errorf("invalid package name: %q", imp.name)
			imp = nil // 在下面创建假包
		}
		if err != nil {
			check.errorf(at, _BrokenImport, "could not import %s (%s)", path, err)
			if imp == nil {
				// 创建一个新的假包
				// 提出一个合理的包名（启发式）
				name := path
				if i := len(name); i > 0 && name[i-1] == '/' {
					name = name[:i-1]
				}
				if i := strings.LastIndex(name, "/"); i >= 0 {
					name = name[i+1:]
				}
				imp = NewPackage(path, name)
			}
			// 继续尽可能地使用该软件包
			imp.fake = true // 避免后续查找失败
		}
	}

	// 包装应完整或标记为假冒，但要谨慎
	if imp.complete || imp.fake {
		check.impMap[key] = imp
		// 一旦我们格式化了一次错误消息，请保留pkgPathMap
		// 后续导入的最新信息。
		if check.pkgPathMap != nil {
			check.markImports(imp)
		}
		return imp
	}

	// 出现问题（进口商可能已无误退回不完整的包装）
	return nil
}

// collectObjects收集并插入所有文件和包对象
// 进入各自的范围。它还执行导入和关联
// 具有接收器基类型名称的方法。
func (check *Checker) collectObjects() {
	pkg := check.pkg

	// pkgImports是任何看到的包文件都已导入的包集
	// 目前为止用于避免pkg.imports中出现重复条目。分配和填充
	// 它（如果我们以增量方式检查测试文件，pkg.imports可能不是空的）。
	// 请注意，pkgImports是由包（以及包路径）设置密钥的，而不是由
	// importKey值。两个不同的importKey值可能映射到同一个包
	// 这就是为什么我们不能在这里使用check.impMap。
	var pkgImports = make(map[*Package]bool)
	for _, imp := range pkg.imports {
		pkgImports[imp] = true
	}

	type methodInfo struct {
		obj  *Func      // 方法
		ptr  bool       // 如果指针接收器为true
		recv *ast.Ident // 接收器类型名称
	}
	var methods []methodInfo // 具有有效接收者和非空名称的已收集方法
	var fileScopes []*Scope
	for fileNo, file := range check.files {
		// 包标识符表示当前包，
		// 但是没有相应的包对象。
		check.recordDef(file.Name, nil)

		// 使用实际的源文件范围，而不是*ast.file范围，因为
		// 后者不包括出现在文件开头或结尾的注释。
		// 如果没有*token.File文件，请保守使用*ast.File扩展名。
		pos, end := file.Pos(), file.End()
		if f := check.fset.File(file.Pos()); f != nil {
			pos, end = token.Pos(f.Base()), token.Pos(f.Base()+f.Size())
		}
		fileScope := NewScope(check.pkg.scope, pos, end, check.filename(fileNo))
		fileScopes = append(fileScopes, fileScope)
		check.recordScope(file, fileScope)

		// 确定解析导入所需的文件目录
		// 文件名可以是“”（通常用于测试），在这种情况下
		// 我们得到“.”作为我们想要的目录。
		fileDir := dir(check.fset.Position(file.Name.Pos()).Filename)

		check.walkDecls(file.Decls, func(d decl) {
			switch d := d.(type) {
			case importDecl:
				// 进口包装
				path, err := validatedImportPath(d.spec.Path.Value)
				if err != nil {
					check.errorf(d.spec.Path, _BadImportPath, "invalid import path (%s)", err)
					return
				}

				imp := check.importPackage(d.spec.Path, path, fileDir)
				if imp == nil {
					return
				}

				// 本地名称覆盖导入的包名称
				name := imp.name
				if d.spec.Name != nil {
					name = d.spec.Name.Name
					if path == "C" {
						// 匹配cmd/compile（规范未规定）
						check.errorf(d.spec.Name, _ImportCRenamed, `cannot rename import "C"`)
						return
					}
				}

				if name == "init" {
					check.errorf(d.spec, _InvalidInitDecl, "cannot import package as init - init must be a func")
					return
				}

				// 将包添加到显式导入列表
				// （提供此功能是为了方便
				// 为客户服务；类型检查不需要它）
				if !pkgImports[imp] {
					pkgImports[imp] = true
					pkg.imports = append(pkg.imports, imp)
				}

				pkgName := NewPkgName(d.spec.Pos(), pkg, name, imp)
				if d.spec.Name != nil {
					// 在点导入中，点表示包
					check.recordDef(d.spec.Name, pkgName)
				} else {
					check.recordImplicit(d.spec, pkgName)
				}

				if path == "C" {
					// 匹配cmd/compile（规范未规定）
					pkgName.used = true
				}

				// 将导入添加到文件范围
				check.imports = append(check.imports, pkgName)
				if name == "." {
					// 点导入
					if check.dotImportMap == nil {
						check.dotImportMap = make(map[dotImportKey]*PkgName)
					}
					// 将导入的作用域与文件作用域合并
					for _, obj := range imp.scope.elems {
						// 包作用域可能包含未导出的对象，
						// 不要输入它们！
						if obj.Exported() {
							// 声明点导入对象
							// （不要使用check.declare，因为它会修改对象。）
							// 通过Object.setScopePos，导致竞争条件；
							// 对象可以导入到多个文件作用域中
							// 同时见第32154期。）
							if alt := fileScope.Insert(obj); alt != nil {
								check.errorf(d.spec.Name, _DuplicateDecl, "%s redeclared in this block", obj.Name())
								check.reportAltDecl(alt)
							} else {
								check.dotImportMap[dotImportKey{fileScope, obj}] = pkgName
							}
						}
					}
				} else {
					// 在文件作用域中声明导入的包对象
					// （无需提供s.名称，因为我们之前调用了check.recordDef）
					check.declare(fileScope, nil, pkgName, token.NoPos)
				}
			case constDecl:
				// 声明所有常量
				for i, name := range d.spec.Names {
					obj := NewConst(name.Pos(), pkg, name.Name, nil, constant.MakeInt64(int64(d.iota)))

					var init ast.Expr
					if i < len(d.init) {
						init = d.init[i]
					}

					d := &declInfo{file: fileScope, vtyp: d.typ, init: init, inherited: d.inherited}
					check.declarePkgObj(name, obj, d)
				}

			case varDecl:
				lhs := make([]*Var, len(d.spec.Names))
				// 如果只有一个rhs初始值设定项，请使用
				// 所有lhs变量的declInfo d1相同
				// 因此，每个lhs变量依赖于相同的
				// rhs初始值设定项（n:1 var声明）。
				var d1 *declInfo
				if len(d.spec.Values) == 1 {
					// lhs元件仅在下面的for循环之后设置，
					// 但这没关系，因为declareVar只收集declInfo
					// 为以后的阶段。
					d1 = &declInfo{file: fileScope, lhs: lhs, vtyp: d.spec.Type, init: d.spec.Values[0]}
				}

				// 声明所有变量
				for i, name := range d.spec.Names {
					obj := NewVar(name.Pos(), pkg, name.Name, nil)
					lhs[i] = obj

					di := d1
					if di == nil {
						// 个人作业
						var init ast.Expr
						if i < len(d.spec.Values) {
							init = d.spec.Values[i]
						}
						di = &declInfo{file: fileScope, vtyp: d.spec.Type, init: init}
					}

					check.declarePkgObj(name, obj, di)
				}
			case typeDecl:
				obj := NewTypeName(d.spec.Name.Pos(), pkg, d.spec.Name.Name, nil)
				check.declarePkgObj(d.spec.Name, obj, &declInfo{file: fileScope, tdecl: d.spec})
			case funcDecl:
				info := &declInfo{file: fileScope, fdecl: d.decl}
				name := d.decl.Name.Name
				obj := NewFunc(d.decl.Name.Pos(), pkg, name, nil)
				if d.decl.Recv.NumFields() == 0 {
					// 正则函数
					if d.decl.Recv != nil {
						check.error(d.decl.Recv, _BadRecv, "method is missing receiver")
						// 视为功能
					}
					if name == "init" || (name == "main" && check.pkg.name == "main") {
						code := _InvalidInitDecl
						if name == "main" {
							code = _InvalidMainDecl
						}
						if tparams := typeparams.Get(d.decl.Type); tparams != nil {
							check.softErrorf(tparams, code, "func %s must have no type parameters", name)
						}
						if t := d.decl.Type; t.Params.NumFields() != 0 || t.Results != nil {
							// TODO（rFindley）这应该是一个硬错误吗？
							check.softErrorf(d.decl, code, "func %s must have no arguments and no return values", name)
						}
					}
					if name == "init" {
						// 不要在包作用域中声明init函数-它们是不可见的
						obj.parent = pkg.scope
						check.recordDef(d.decl.Name, obj)
						// init函数必须有一个主体
						if d.decl.Body == nil {
							// TODO（gri）使此错误消息与上述其他错误消息一致
							check.softErrorf(obj, _MissingInitBody, "missing function body")
						}
					} else {
						check.declare(pkg.scope, d.decl.Name, obj, token.NoPos)
					}
				} else {
					// 方法

					// TODO（rFindley）此代码的早期版本检查了
					// 没有类型参数，但稍后将对此进行检查
					// 当类型检查函数类型时。证实
					// 我们不需要在这里检查tparams。

					ptr, recv, _ := check.unpackRecv(d.decl.Recv.List[0].Type, false)
					// （具有无效接收器的方法不能与类型关联，并且
					// 从未找到名称为空的方法；不需要收集任何
					// 是的。它们仍将与所有其他函数一起进行类型检查。）
					if recv != nil && name != "_" {
						methods = append(methods, methodInfo{obj, ptr, recv})
					}
					check.recordDef(d.decl.Name, obj)
				}
				// 方法不是包级别的对象，但我们仍然在
				// 对象映射，以便我们可以像处理常规函数一样处理它们（如果
				// 接收方无效）；在关联时，我们还需要他们的fdecl信息
				// 他们与他们的接收器基地类型，如下。
				check.objMap[obj] = info
				obj.setOrder(uint32(len(check.objMap)))
			}
		})
	}

	// 验证包和文件作用域中的对象是否具有不同的名称
	for _, scope := range fileScopes {
		for _, obj := range scope.elems {
			if alt := pkg.scope.Lookup(obj.Name()); alt != nil {
				if pkg, ok := obj.(*PkgName); ok {
					check.errorf(alt, _DuplicateDecl, "%s already declared through import of %s", alt.Name(), pkg.Imported())
					check.reportAltDecl(pkg)
				} else {
					check.errorf(alt, _DuplicateDecl, "%s already declared through dot-import of %s", alt.Name(), obj.Pkg())
					// TODO（gri）点导入的对象没有位置；reportAltDecl不会打印任何内容
					check.reportAltDecl(obj)
				}
			}
		}
	}

	// 现在我们有了所有包范围对象和所有方法，
	// 尽可能将方法与接收器基类型名称关联。
	// 忽略具有无效接收器的方法。他们会的
	// 稍后使用常规函数检查类型。
	if methods == nil {
		return // 无事可做
	}
	check.methods = make(map[*TypeName][]*Func)
	for i := range methods {
		m := &methods[i]
		// 确定接收器基本类型并将m与之关联。
		ptr, base := check.resolveBaseTypeName(m.ptr, m.recv)
		if base != nil {
			m.obj.hasPtrRecv = ptr
			check.methods[base] = append(check.methods[base], m.obj)
		}
	}
}

// 解包recv解包接收器类型并返回其组件：ptr指示是否
// rtyp是指针接收器，rname是接收器类型名称，tparams是其
// 类型参数（如果有）。仅当unpackParams为
// 设置如果rname为nil，则接收器不可用（即，源有一个bug，我们需要
// 无法轻松解决）。
func (check *Checker) unpackRecv(rtyp ast.Expr, unpackParams bool) (ptr bool, rname *ast.Ident, tparams []*ast.Ident) {
L: // 拆包接收器类型
	// 它接受无效接收器，如***T，但不接受
	// 为其他无效接收者工作，但我们不在乎。这个
	// 接收方表达式的有效性在别处检查。
	for {
		switch t := rtyp.(type) {
		case *ast.ParenExpr:
			rtyp = t.X
		case *ast.StarExpr:
			ptr = true
			rtyp = t.X
		default:
			break L
		}
	}

	// 解包类型参数（如果有）
	if ptyp, _ := rtyp.(*ast.IndexExpr); ptyp != nil {
		rtyp = ptyp.X
		if unpackParams {
			for _, arg := range typeparams.UnpackExpr(ptyp.Index) {
				var par *ast.Ident
				switch arg := arg.(type) {
				case *ast.Ident:
					par = arg
				case *ast.BadExpr:
					// 忽略-分析器已报告错误
				case nil:
					check.invalidAST(ptyp, "parameterized receiver contains nil parameters")
				default:
					check.errorf(arg, _Todo, "receiver type parameter %s must be an identifier", arg)
				}
				if par == nil {
					par = &ast.Ident{NamePos: arg.Pos(), Name: "_"}
				}
				tparams = append(tparams, par)
			}
		}
	}

	// 解包接收方名称
	if name, _ := rtyp.(*ast.Ident); name != nil {
		rname = name
	}

	return
}

// resolveBaseTypeName返回类型的非别名基类型名称，以及
// 有一个指向它的间接指针。必须声明基类型名称
// 在包范围内，最多可以有一个指针间接寻址。如果没有这种类型
// 名称存在，返回的基数为零。
func (check *Checker) resolveBaseTypeName(seenPtr bool, name *ast.Ident) (ptr bool, base *TypeName) {
	// 算法：从类型表达式开始，可以是名称，
	// 我们通过别名声明遵循该类型，直到到达
	// 非别名类型名称。如果我们遇到指针类型或
	// 括号我们完成了。如果遇到多个指针类型
	// 我们结束了。
	ptr = seenPtr
	var seen map[*TypeName]bool
	var typ ast.Expr = name
	for {
		typ = unparen(typ)

		// 检查是否有指针类型
		if pexpr, _ := typ.(*ast.StarExpr); pexpr != nil {
			// 如果我们已经看到了指针，我们就完成了
			if ptr {
				return false, nil
			}
			ptr = true
			typ = unparen(pexpr.X) // 继续使用指针基类型
		}

		// typ必须是一个名称
		name, _ := typ.(*ast.Ident)
		if name == nil {
			return false, nil
		}

		// 名称必须表示在当前包范围内找到的对象
		// （请注意，dot导入的对象不在包范围内！）
		obj := check.pkg.scope.Lookup(name.Name)
		if obj == nil {
			return false, nil
		}

		// 对象必须是类型名。。。
		tname, _ := obj.(*TypeName)
		if tname == nil {
			return false, nil
		}

		// ... 这是我们以前从未见过的
		if seen[tname] {
			return false, nil
		}

		// 如果tdecl将tname定义为新类型，那么我们就完成了
		// （而不是别名）
		tdecl := check.objMap[tname].tdecl // 包作用域中的对象必须存在
		if !tdecl.Assign.IsValid() {
			return ptr, tname
		}

		// 否则，请继续解决
		typ = tdecl.Type
		if seen == nil {
			seen = make(map[*TypeName]bool)
		}
		seen[tname] = true
	}
}

// PackageObject类型检查所有包对象，但不检查函数体。
func (check *Checker) packageObjects() {
	// 按源顺序处理包对象，以获得可重复的结果
	objList := make([]Object, len(check.objMap))
	i := 0
	for obj := range check.objMap {
		objList[i] = obj
		i++
	}
	sort.Sort(inSourceOrder(objList))

	// 将新方法添加到已检查的类型（来自先前的Checker.Files调用）
	for _, obj := range objList {
		if obj, _ := obj.(*TypeName); obj != nil && obj.typ != nil {
			check.collectMethods(obj)
		}
	}

	// 我们首先处理非别名声明，以避免出现以下情况：
	// 别名声明的类型在可用之前是必需的。一般来说
	// 这仍然不够，因为有可能产生足够复杂的问题
	// 递归类型定义，这将导致在其之前需要一个类型别名
	// 可用（示例见第25838期）。
	// 另一方面，cmd/编译器也有同样的问题（#25838）。
	var aliasList []*TypeName
	// 第一阶段
	for _, obj := range objList {
		// 如果我们有一个类型别名，请在第二阶段收集它。
		if tname, _ := obj.(*TypeName); tname != nil && check.objMap[tname].tdecl.Assign.IsValid() {
			aliasList = append(aliasList, tname)
			continue
		}

		check.objDecl(obj, nil)
	}
	// 第二阶段
	for _, obj := range aliasList {
		check.objDecl(obj, nil)
	}

	// 此时，我们可能有一个非空的check.methods映射；这意味着并非所有
	// 在typeDecl的末尾删除了条目，因为相应的接收方基础
	// 找不到类型。在这种情况下，在声明这些
	// 方法。我们现在可以安全地丢弃这张地图了。
	check.methods = nil
}

// inSourceOrder实现sort.sort接口。
type inSourceOrder []Object

func (a inSourceOrder) Len() int           { return len(a) }
func (a inSourceOrder) Less(i, j int) bool { return a[i].order() < a[j].order() }
func (a inSourceOrder) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

// 未使用的导入检查未使用的导入。
func (check *Checker) unusedImports() {
	// 如果未检查函数体，则可能缺少包的使用-不要检查
	if check.conf.IgnoreFuncBodies {
		return
	}

	// spec:“直接导入包而不引用
	// 它的任何导出标识符。仅因其副作用而导入包装
	// （初始化），将空白标识符用作显式包名。

	for _, obj := range check.imports {
		if !obj.used && obj.name != "_" {
			check.errorUnusedPkg(obj)
		}
	}
}

func (check *Checker) errorUnusedPkg(obj *PkgName) {
	// 如果导入的包的名称不是final
	// 导入路径元素，在错误消息中显式显示它。
	// 请注意，这将处理重命名的导入和的导入
	// 包含非常规包声明的包。
	// 请注意，即使在Windows上，这也会使用/始终，因为转到导入
	// 路径始终使用前斜杠。
	path := obj.imported.path
	elem := path
	if i := strings.LastIndex(elem, "/"); i >= 0 {
		elem = elem[i+1:]
	}
	if obj.name == "" || obj.name == "." || obj.name == elem {
		check.softErrorf(obj, _UnusedImport, "%q imported but not used", path)
	} else {
		check.softErrorf(obj, _UnusedImport, "%q imported but not used as %s", path, obj.name)
	}
}

// dir真诚地尝试返回目录
// 路径的一部分。如果路径为空，则结果为“”。
// （根据go/build包依赖性测试，我们无法导入
// path/filepath，只需使用filepath.Dir。）
func dir(path string) string {
	if i := strings.LastIndexAny(path, `/\`); i > 0 {
		return path[:i]
	}
	// i<=0
	return "."
}
